#!/usr/bin/python
from visual import *
from TimeChessUtil import *
from subprocess import *
import json


class Piece(frame):
	def __init__(self, pos, color, ptype, *args, **kwargs):
		super(Piece,self).__init__(*args, **kwargs)
		self.color = color
		if self.color=='b':	col = (.2,.2,.2)
		else:				col = (1,1,1)

		#set forwards direction
		self.forwards = -1 if self.color == 'b' else 1

		self.Pos = pos
		self.pos = (pos.x,pos.y,0)

		# Define the 3D geometries of the pieces
		csize = .15 #crenelation size
		geometries = {
				"p":[
							("cone",	{"pos":(0,0,0), "axis":(0,0,.5), "radius":.3}),
							("sphere",	{"pos":(0,0,.5), "radius":.15})
					],
				"r":[
							("cylinder",	{"pos":(0,0,0), "axis":(0,0,.7), "radius":.3}),
							("box",	{"pos":(+.3-(csize/2),0,.7+(csize/2)), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(-.3+(csize/2),0,.7+(csize/2)), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(0,+.3-(csize/2),.7+(csize/2)), "width":csize, "height":csize, "length":csize}),
							("box",	{"pos":(0,-.3+(csize/2),.7+(csize/2)), "width":csize, "height":csize, "length":csize}),
					],
				"n":[
							("box",	{"pos":(0,0,.4), "width":.8, "height":.3, "length":.3}),
							("box",	{"pos":(0,self.forwards*.2,.5), "axis":(0,self.forwards,-1),
										"width":.3, "height":.3, "length":.5}),
					],
				"b":[
						("cylinder", {"pos":(0,0,0), "axis":(0,0,.6), "radius":.15}),
						("cone", {"pos":(0,0,.65), "axis":(0,0,.3), "radius":.2}),
						("cone", {"pos":(0,0,.65), "axis":(0,0,-.3), "radius":.2}),
					],
				"q":[
						("cylinder", {"pos":(0,0,0), "axis":(0,0,.7), "radius":.2} ),
						("cone",{"pos":(0,0,.9), "axis":(0,0,-.6), "radius":.3}),
						("sphere", {"pos":(0,0,.9), "radius":.05}),
					],
				"k":[
						("cylinder", {"pos":(0,0,0), "axis":(0,0,.7), "radius":.2} ),
						("box", {"pos":(0,0,.85), "width":.1, "height":.1, "length":.3, "axis":(1,0,0)}),
						("box", {"pos":(0,0,.85), "width":.1, "height":.1, "length":.3, "axis":(0,0,1)}),
					]
		}



		for prim, args in geometries[ptype]:
			visual.__dict__[prim](frame=self, color=col,**args)

	def getPos(self):
		return self.Pos

class Ghost(Piece):
	"""
	A 'not really there' piece to represent targets of future time travelling pieces
	"""
	def __init__(self,*args,**kwargs):
		super(Ghost,self).__init__(*args,**kwargs)
		for obj in self.objects:
			obj.opacity = 0.2



class Layer(frame):
	def __init__(self, z, turnMade):
		super(Layer,self).__init__()
		self.turnMade = turnMade
		self.pos = (0,0,z)
		self.grid = []
		self.pieces = dict([])
		self.grid.append(curve(frame=self,pos=[(-.5,-.5,0),(-.5,7.5,0),(7.5,7.5,0),(7.5,-.5,0)]))
#		for x in range(8):
#			label(pos=(x,z,-1), text="abcdefgh"[x], box=False, opacity=0)
#			label(pos=(x,z,8), text="abcdefgh"[x], box=False, opacity=0)
		for y in range(8):
#			label(pos=(-1,z,y), text=(str)(y+1), box=False, opacity=0)
#			label(pos=(8,z,y), text=(str)(y+1), box=False, opacity=0)
			self.grid.append(curve(frame=self, pos=[(-.5,y-.5,0),(7.5,y-.5,0)]))
			for x in range(8):
				self.grid.append(curve(frame=self,pos=[(x-.5,-.5,0),(x-.5,7.5,0)]))
	
	def newPiece(self, posStr, color, ptype):
		debug("making new piece in this layer")
		pos = Pos(posStr)
		self.pieces[pos] = Piece(pos, color, ptype, frame=self)

		

class Controller(object):
	def __init__(self, engineCommand):
		self.t = 0
		self.engine = Popen(engineCommand,stdin=PIPE,stdout=PIPE)
#		print engine.stdout.readline()
		self.layers = [None]*500
		self.buildLayer(0)

		self.selected = None
		self.hls = []
		self.ghosts = []
	
	def send(self, message):
		debug("sending \"%s\""%message)
		self.engine.stdin.write(message+"\n")
		self.engine.stdin.flush()

	def recv(self):
		message = self.engine.stdout.readline()
		debug("recieved \"%s\""%message)
		return message

	def communicate(self, message):
		self.send(message)
		return self.recv()

	def hideAllLayers(self,exceptPresent=False):
		for i, layer in enumerate(self.layers):
			if layer:
				if (not exceptPresent) or (i != self.t):
					layer.visible = False

	def hideLayer(self, t):
		if self.layers[t]:
			self.layers[t].visible = False

	def showLayer(self, t):
		"""
		Show a layer of the board

		If an up to date 3d object for the layer exists, show it
		Otherwise, ask the engine for the layer and build it using that data
		"""

		debug("Attempting to show layer %s"%t)

		# If there's nothing in the layer buffer, build layer from engine data
		if not self.layers[t]:
			self.buildLayer(t)

		# if there's something there but it's out of date, remove it and replace
		elif self.layers[t] and self.layers[t].turnMade < self.t:
			self.layers[t].visible = False
			self.layers[t] = None
			self.buildLayer(t)

		# if there's something up to date, make sure it's visible
		elif self.layers[t] and self.layers[t].turnMade >= self.t:
			self.layers[t].visible = True

	def buildLayer(self, t):
		"""
		Build a layer of 3d objects using data fetched from the engine
		"""
		#Make a new Layer object
		newLayer = Layer(t, self.t)

		# Ask the engine for the state of the layer
		layerString = self.communicate("getState %d" % t).partition(":")[2].strip()
		#convert it from JSON
		layerData = json.loads(layerString)

		for p in layerData:
			debug(str(p))
			newLayer.newPiece(*p)

		self.layers[t] = newLayer


	def quit(self):
		debug("sending quit signal to engine")
		self.send("quit")

	def __del__(self):
		self.quit()
	
	def clearHighlights(self):
		for hl in self.hls:
			hl.visible = False
		self.hls = []

	def makeHighlight(self, pos):
		hl = box(pos=(pos.x,pos.y,pos.t),color=(1,0,0),opacity=0.2,width=.001,height=1,length=1)
		self.hls.append(hl)

	def updateGhosts(self):
		for g in self.ghosts:
			g.visible = False
		self.ghosts = []

		gstring = self.communicate("getState futureQueue").partition(":")[2].strip()
		gdata = json.loads(gstring)

		for posstr, color, ptype in gdata:
			pos = Pos(posstr)
			g = Ghost(pos, color, ptype)
			g.z = pos.t
			self.ghosts.append(g)

	def select(self, piece):
		self.selected = piece
		scene.center = (piece.x,piece.y,piece.getPos().t)

		#ask the engine what moves are available to selected piece
		instr = self.communicate(str(piece.getPos()))

		# parse the moves into a list of position strings
		datastring = instr.partition(":")[2]
		try:
			posstrs = json.loads(datastring)
		except ValueError as e:
				debug("reading highlight pos exception: %s, datastring: %s" (e, instr))


		# Reset the display of the board by hiding all layers except the present
		# and clearing all highlights
		self.clearHighlights()
		self.hideAllLayers(exceptPresent=True)	

		# make the highlights and make sure each layer with a highlight is shown
		for posstr in posstrs:
			pos = Pos(posstr)
			self.makeHighlight(pos)
			self.showLayer(pos.t)

			

	def gameLoop(self):
		while True:
			debug("Turn: %s" % self.t)

			self.currentColor = 'w' if self.t % 2 == 0 else 'b'
			debug("%s's Turn" % self.currentColor)

			scene.center = (3.5,3.5,self.t)

			# This loop keeps going around handling input until
			# a valid move is made
			moveMade = False
			while not moveMade:
				rate(20)

				# This needs to be a loop in case multiple mouse events happen quickly
				while scene.mouse.clicked:
					evt = scene.mouse.getclick()

					# grab the object that's under the cursor when the mouse is clicked
					obj = scene.mouse.pick

					if obj:
						if (type(obj.frame) == Piece):
							piece = obj.frame
							pos = Pos((piece.x,piece.y,piece.z))

							# if the piece is the colour whose turn it is
							if piece.color == self.currentColor:

								# and it isn't already selected, select it
								if piece != self.selected:
									self.select(piece)

								# If it is selected, deselect it and clear its highlights
								else:
									self.selected = None
									self.clearHighlights()
						elif type(obj) == box: #it's a position
							startpos = self.selected.getPos()
							endpos = Pos((obj.x,obj.y,obj.z))

							# Tell the engine to make the move signified
							result = self.communicate("%s%s" % (startpos, endpos))


							# Update the turn from the engine. This will have changed if the move worked
							self.t = int(self.communicate("getState t"))


							# If it's turn 0, either we just started or a new game just began
							# Either way we can throw away the layer cache and start fresh
							if (self.t == 0):
								self.layers = [None]*500
								self.hideAllLayers()
								self.showLayer(self.t)
								self.clearHighlights()
								self.updateGhosts()

							if result.strip() == "Success":
								self.hideAllLayers()
								self.showLayer(self.t)
								self.clearHighlights()
								self.updateGhosts()
								moveMade = True
							else:
								debug("3d game controller: Move failed: %s"%result)

				if scene.kb.keys:
					s = scene.kb.getkey()
					if s == "n":
						self.hideAllLayers(exceptPresent=True)
					elif s == "p":
						for t in range(0,self.t):
							self.hideLayer(t)
					elif s == "f":
						for t in range(self.t+1,len(self.layers)):
							self.hideLayer(t)
					elif s == "s":
						if scene.stereo != 'redblue':
							scene.stereo = 'redblue'
						else:
							scene.stereo = 'nostereo'




import traceback
import sys

if __name__ == "__main__":
	gc = None
	try:
		if len(sys.argv) > 1:
			gc = Controller(sys.argv[1])
		else:
			gc = Controller("TCECPInterface.py")
		scene.up = (0,0,1)
		gc.gameLoop()
	except Exception as e:
		traceback.print_exc()
		if gc: gc.quit()
		sys.exit()
	if gc: gc.quit()
